package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/buffer"
	"go.uber.org/zap/zapcore"
	"os"
)

// levelEncoder 自定义日志编码器结构体
// 功能：扩展 zapcore.Encoder 接口，实现按日志级别进行不同处理
// 字段说明：
// - zapcore.Encoder: 内嵌原始编码器，保留原有功能
// - errFile: 错误日志文件指针，用于单独存储错误级别日志
type levelEncoder struct {
	zapcore.Encoder
	errFile *os.File
}

// EncodeEntry 自定义日志条目编码方法
// 参数说明：
// - entry: 日志条目，包含时间戳、级别、消息等基本信息
// - fields: 额外的字段信息，用于结构化日志
// 返回值：
// - *buffer.Buffer: 包含编码后日志数据的缓冲区
// - error: 编码过程中的错误信息
// 功能：重写编码器的 EncodeEntry 方法，根据日志级别进行特殊处理
func (e levelEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
	// 1. 先调用原始编码器进行基础编码（时间、级别、消息等格式化）
	buff, err := e.Encoder.EncodeEntry(entry, fields)
	if err != nil {
		return buff, err // 如果编码出错，直接返回错误
	}

	// 2. 根据日志级别进行不同的处理
	switch entry.Level {
	case zapcore.ErrorLevel: // 如果是错误级别（ErrorLevel）的日志
		// 延迟初始化错误日志文件（第一次遇到错误日志时创建）
		if e.errFile == nil {
			// 文件模式：存在则追加，不存在则创建，只写模式
			// os.O_CREATE: 文件不存在时创建
			// os.O_APPEND: 追加模式
			// os.O_WRONLY: 只写模式
			file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
			e.errFile = file
		}
		// 将错误日志写入专门的错误日志文件
		_, err := e.errFile.WriteString(buff.String())
		if err != nil {
			return nil, err
		}
	}

	// 返回编码后的缓冲区供控制台输出使用
	return buff, nil
}

// main 函数演示按日志级别进行不同处理的实现
// 通过自定义编码器实现错误日志的单独存储
func main() {
	// 创建日志核心组件
	core := zapcore.NewCore(
		// 使用自定义编码器（基于控制台编码器）
		levelEncoder{
			Encoder: zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
		},
		os.Stdout,         // 日志输出到标准输出（控制台）
		zapcore.InfoLevel, // 设置日志级别为Info及以上（Debug级别日志将被过滤）
	)

	// 创建日志记录器实例
	logger := zap.New(
		core,            // 传入配置好的核心组件
		zap.AddCaller(), // 添加调用者信息（文件名和行号），便于调试定位
	)

	// 记录不同级别的日志进行测试
	logger.Debug("this is prod debug log") // 调试信息（由于级别设置为Info，此日志不会被记录）
	logger.Info("this is prod info log")   // 普通信息（正常记录到控制台）
	logger.Warn("this is prod warn log")   // 警告信息（正常记录到控制台）
	logger.Error("this is prod error log") // 错误信息（同时记录到控制台和app.log文件）
}
